﻿using OpenCvSharp;
using OpenCvSharp.Extensions;
using OpenCvSharp.XFeatures2D;
using System;
using System.Collections.Generic;
using System.Drawing;
using Point = OpenCvSharp.Point;

namespace Core.GameTool.OpenCV
{
    public class Opencvsharp
    {
        public static void ShowImg(string src)
        {
            Mat img = new Mat(src);
            Cv2.ImShow("Test",img);
            Cv2.WaitKey();
        }
        public static void ShowImg(System.Drawing.Bitmap btp)
        {
            Mat mat = BitmapConverter.ToMat(btp);
            Cv2.ImShow("Test", mat);
            Cv2.WaitKey();
        }

        public static Point MatchTemplate(string original,string model)
        {
            var originalMat = new Mat(original, ImreadModes.AnyColor) ;  //母图
            var modelMat = new Mat(model, ImreadModes.AnyColor) ;      //模板
            var resultMat = new Mat();

            resultMat.Create(originalMat.Cols - modelMat.Cols + 1, originalMat.Rows - modelMat.Cols + 1, MatType.CV_32FC1);//创建result的模板，就是MatchTemplate里的第三个参数

            Cv2.MatchTemplate(originalMat, modelMat, resultMat, TemplateMatchModes.SqDiff);//进行匹配(1母图,2模版子图,3返回的result，4匹配模式)
            Cv2.MinMaxLoc(resultMat, out OpenCvSharp.Point minLocation, out OpenCvSharp.Point maxLocation);

            var p2 = new Point(minLocation.X + modelMat.Cols, minLocation.Y + modelMat.Rows);
            Cv2.Rectangle(originalMat, minLocation, p2, Scalar.Red, 2); //画出匹配的矩
            return new Point((p2.X + minLocation.X) / 2, (p2.Y + minLocation.Y) / 2);
        }
        public static double MatchTemplate(System.Drawing.Bitmap original, string model, out System.Drawing.Point point, TemplateMatchModes template= TemplateMatchModes.CCoeff,bool isshow = false)
        {
            var originalMat = original.ToMat().CvtColor(ColorConversionCodes.RGBA2BGR);  //母图
            var modelMat = new Mat(model,ImreadModes.AnyColor);//模板
            var resultMat = new Mat();
            resultMat.Create(originalMat.Cols - modelMat.Cols + 1, originalMat.Rows - modelMat.Cols + 1, MatType.CV_32FC1);//创建result的模板，就是MatchTemplate里的第三个参数

            Cv2.MatchTemplate(originalMat, modelMat, resultMat, template);//进行匹配(1母图,2模版子图,3返回的result，4匹配模式)
            Cv2.MinMaxLoc(resultMat, out double minVal, out double maxVal, out Point minLocation, out Point maxLocation);
            var p2 = new Point(minLocation.X + modelMat.Cols, minLocation.Y + modelMat.Rows);
            if (isshow)
            {
                Cv2.Rectangle(originalMat, minLocation, p2, Scalar.Red, 2); //画出匹配的矩
                Cv2.ImShow("123", originalMat);
                Cv2.WaitKey();
            }
            point= new System.Drawing.Point((p2.X + minLocation.X) / 2, (p2.Y + minLocation.Y) / 2);
            return minVal;
        }

        public static Bitmap DrawRectangle(Bitmap bitmap,Point p1,Point p2,int thickness=1)
        {
            var originalMat = bitmap.ToMat().CvtColor(ColorConversionCodes.RGBA2BGR);
            Cv2.Rectangle(originalMat, p1, p2, Scalar.Red,thickness);
            return originalMat.ToBitmap();
        }

        //This method may be missed, you may read a lot of blogs, but none of them wrote
        private static Point2d Point2fToPoint2d(Point2f input)
        {
            Point2d p2 = new Point2d(input.X, input.Y);
            return p2;
        }


        public static Bitmap MatchPicBySurf(string original, string model, double threshold = 400)
        {
            var matSrc = new Mat(original, ImreadModes.Color);  //母图
            using var matTo = new Mat(model, ImreadModes.Color);//模板
            using var matSrcRet = new Mat();
            using var matToRet = new Mat();
            KeyPoint[] keyPointsSrc, keyPointsTo;
            using (var surf = OpenCvSharp.XFeatures2D.SURF.Create(threshold, 4, 2, true, true))
            {
                surf.DetectAndCompute(matSrc, null, out keyPointsSrc, matSrcRet);
                surf.DetectAndCompute(matTo, null, out keyPointsTo, matToRet);
            }
            using var flnMatcher = new OpenCvSharp.FlannBasedMatcher();
            var matches = flnMatcher.Match(matSrcRet, matToRet);
            //Finding the Minimum and Maximum Distance
            double minDistance = 100;//Backward approximation
            double maxDistance = 0;
            for (int i = 0; i < matSrcRet.Rows; i++)
            {
                double distance = matches[i].Distance;
                if (distance > maxDistance)
                {
                    maxDistance = distance;
                }
                if (distance < minDistance)
                {
                    minDistance = distance;
                }
            }
            Console.WriteLine($"max distance : {maxDistance}");
            Console.WriteLine($"min distance : {minDistance}");

            var pointsSrc = new List<Point2f>();
            var pointsDst = new List<Point2f>();
            //Screening better matching points
            var goodMatches = new List<DMatch>();
            for (int i = 0; i < matSrcRet.Rows; i++)
            {
                double distance = matches[i].Distance;
                if (distance < Math.Max(minDistance * 2, 0.04))
                {
                    pointsSrc.Add(keyPointsSrc[matches[i].QueryIdx].Pt);
                    pointsDst.Add(keyPointsTo[matches[i].TrainIdx].Pt);
                    //Compression of new ones with distances less than ranges DMatch
                    goodMatches.Add(matches[i]);
                }
            }

            var outMat = new Mat();

            // algorithm RANSAC Filter the matched results
            var pSrc = pointsSrc.ConvertAll(Point2fToPoint2d);
            var pDst = pointsDst.ConvertAll(Point2fToPoint2d);
            var outMask = new Mat();
            // If the original matching result is null, Skip the filtering step
            if (pSrc.Count > 0 && pDst.Count > 0)
                Cv2.FindHomography(pSrc, pDst, HomographyMethods.Ransac, mask: outMask);
            // If passed RANSAC After processing, the matching points are more than 10.,Only filters are used. Otherwise, use the original matching point result(When the matching point is too small, it passes through RANSAC After treatment,It's possible to get the result of 0 matching points.).
            if (outMask.Rows > 10)
            {
                //byte[] maskBytes = new byte[outMask.Rows * outMask.Cols];
                outMask.GetArray(out byte[] maskBytes);
                Cv2.DrawMatches(matSrc, keyPointsSrc, matTo, keyPointsTo, goodMatches, outMat, matchesMask: maskBytes, flags: DrawMatchesFlags.Default);
            }
            else
                Cv2.DrawMatches(matSrc, keyPointsSrc, matTo, keyPointsTo, goodMatches, outMat, flags: DrawMatchesFlags.NotDrawSinglePoints);
            Cv2.ImShow("Test", outMat);
            Cv2.WaitKey();
            return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(outMat);
        }
    }
}
